"""
    价量因子：
    1. 成交稳定性
        - 换手率变异系数
    2. 流动性
        - 换手率
    3. 交易拥挤度
        - 量价相关性：成交量和复权收盘价秩相关系数
        - 加权偏度：成交量加权收盘价偏度
"""
import pandas as pd
import numpy as np
from scipy.stats import rankdata
from numpy.lib.stride_tricks import sliding_window_view


# 换手率变异系数
def cv_turnover(group, window_size):
    """
        换手率变异系数：衡量换手率的波动性
        公式定义：
        变异系数 = group / (std / mean)，其中：
            - group 表示当前窗口的换手率序列
            - mean 表示该窗口的均值
            - std 表示该窗口的标准差
    """
    if len(group) < window_size:
        return pd.Series(np.nan, index=group.index)

    # 滚动窗口计算均值和标准差
    rolling_mean = group.rolling(window_size).mean()
    rolling_std = group.rolling(window_size).std()

    # 根据公式计算变异系数，避免除以零
    cv_series = np.where((rolling_mean != 0) & (rolling_std != 0), group / (rolling_std / rolling_mean), np.nan)

    return pd.Series(cv_series, index=group.index)


# 量价相关性：成交量与复权收盘价秩相关系数
def corr_volumePrice(group, window_size):
    """
        量价相关性
        prams group: columns包含vol, close
    """
    if len(group) < window_size:
        group['signal_relative'] = np.nan
        return group

    # 处理滑动窗口逻辑
    _vol = group['vol'].values
    _close = group['close'].values

    _vol_window = sliding_window_view(_vol, window_size)
    _close_window = sliding_window_view(_close, window_size)

    # 对每个窗口进行秩转换
    x_ranks = np.apply_along_axis(rankdata, 1, _vol_window)
    y_ranks = np.apply_along_axis(rankdata, 1, _close_window)
    # 中心化秩
    x_ranks_centered = x_ranks - np.mean(x_ranks, axis=1, keepdims=True)
    y_ranks_centered = y_ranks - np.mean(y_ranks, axis=1, keepdims=True)

    # 计算 秩的Pearson 相关系数（即 Spearman）
    numerator = np.sum(x_ranks_centered * y_ranks_centered, axis=1)
    denominator = np.sqrt(np.sum(x_ranks_centered ** 2, axis=1) * np.sum(y_ranks_centered ** 2, axis=1))
    corr_values = numerator / (denominator + 1e-8)  # 防止除以零

    # 构造结果 Series，前面 window_size - 1 个值为 NaN
    result = np.empty(len(group))
    result[:window_size - 1] = np.nan
    result[window_size - 1:] = corr_values

    group['signal_relative'] = result

    return group


# 偏度系列
def skew(group, method='vol_return', window_size=60):
    """
        偏度用于衡量尾部方向
        右偏：代表获得右侧概率低，但可能出现很高收益，收益率下降（左侧）概率高但是下跌幅度有限，有利于投资者；
        左偏：代表获得左侧概率低，但可能出现大幅亏损，收益率上涨（右侧）概率高但是上涨幅度有限，不利于投资者；
        prams method: vol_return: 成交量加权收益率偏度；vol_price: 成交量加权后复权收盘价偏度；return: 收益率偏度；price：后复权收盘价偏度
    """
    if method not in ['vol_return', 'vol_price', 'return', 'price']:
        raise ValueError('method must be vol_return, vol_price, return or price')

    def _skew(data: np.ndarray):
        # 计算每行的均值
        mean = np.mean(data, axis=1, keepdims=True)
        # 计算每行的标准差（总体标准差）
        std = np.std(data, axis=1, ddof=0)
        # 计算每行的三阶中心矩
        third_moment = np.mean((data - mean) ** 3, axis=1)
        # 防止除零错误
        skewness = np.where(std != 0, third_moment / (std ** 3), np.nan)
        return skewness

    if method == 'vol_return':
        if len(group) < window_size:
            group['signal_volReturn'] = np.nan
        else:
            _vol_window = sliding_window_view(group['vol'].values, window_size)  # shape: (3641, 60)
            _pct_chg_window = sliding_window_view(group['pct_chg'].values, window_size)  # shape: (3641, 60)
            _signal = _vol_window * _pct_chg_window
            sk = _skew(_signal)

            result = np.empty(len(group))
            result[:window_size - 1] = np.nan
            result[window_size - 1:] = sk
            group['signal_volReturn'] = result

    elif method == 'vol_price':
        if len(group) < window_size:
            group['signal_volClose'] = np.nan
        else:
            _vol_window = sliding_window_view(group['vol'].values, window_size)  # shape: (3641, 60)
            _close_window = sliding_window_view(group['close'].values, window_size)  # shape: (3641, 60)
            _signal = _vol_window * _close_window
            sk = _skew(_signal)

            result = np.empty(len(group))
            result[:window_size - 1] = np.nan
            result[window_size - 1:] = sk
            group['signal_volClose'] = result

    elif method == 'return':
        group['signal_priceReturn'] = group['pct_chg'].rolling(window_size).apply(lambda x: x.skew())

    else:
        group['signal_close'] = group['close'].rolling(window_size).apply(lambda x: x.skew())

    return group

